/*
 Copyright 2023 YaoZinan   zinan@outlook.com   an-archi.cc

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

      https://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 */

#ifndef B3F5A80A_3838_4404_840C_1563CE17D70B
#define B3F5A80A_3838_4404_840C_1563CE17D70B
#include <arpa/inet.h>
#include <assert.h>
#include <errno.h>
#include <event2/buffer.h>
#include <event2/bufferevent.h>
#include <event2/event.h>
#include <event2/listener.h>
#include <event2/util.h>
#include <list>
#include <map>
#include <memory>
#include <netinet/in.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <unordered_map>

#include "config.h"
#include "msg.h"

namespace lrp {
class Tunnel;
class Spot;

enum class Station {
    Server,
    Client
};

struct TunnelMgmt final {
    TunnelMgmt( const Station& st_ );
    ~TunnelMgmt();

    void close( int chan_ );
    void tunnel_unimped( int chan_ );

    // lrps listener station.
    int create_srv_spot( uint16_t port_, bool main_ );

    // link lrpc to *
    Tunnel* create_cli_tunnel( const char* srv_ip_, uint16_t srv_port_, int chan_ );
    int     forward( int index_, const void* data_, int length_ );

    int                init();
    void               run();
    void               shutdown_spots();
    struct event_base* ev_base() { return _ev_base; }
    Station            station() { return _sta; }
    void               remove_pending( const Tunnel* t_ );
    void               add_pending( const Tunnel* t_ );

protected:
    static int  gen_id();
    static void event_cb( struct bufferevent* bev_, short events_, void* user_data_ );
    static void read_cb( struct bufferevent* bev_, void* user_data_ );

    static void listener_cb( struct evconnlistener* listener_,
                             evutil_socket_t        fd_,
                             struct sockaddr*       sa_,
                             int                    socklen_,
                             void*                  user_data_ );
    static void timer_cb( evutil_socket_t fd, short event, void* arg );

private:
    Tunnel* tunnel_by_id( int id_ );
    void    create_timer();

private:
    using TunnelRepo = std::unordered_map<int, Tunnel*>;

    TunnelRepo _tunnels;

    Station            _sta;
    std::list<Spot*>   _srv_spots;
    std::list<Tunnel*> _pending_cli;
    struct event_base* _ev_base;
    struct event*      _timer;
    static int         _s_id;
};

struct Spot {
    Spot( TunnelMgmt& mgmt_, evconnlistener* lis_, bool main_ )
        : _mgmt( mgmt_ )
        , _lis( lis_ )
        , _main( main_ ) {
    }

    TunnelMgmt&     mgmt() { return _mgmt; }
    bool            is_main() const { return _main; }
    void            set_listener( evconnlistener* lis_ ) { _lis = lis_; }
    evconnlistener* listener() { return _lis; }

private:
    TunnelMgmt&     _mgmt;
    evconnlistener* _lis;
    bool            _main;
};

constexpr int kTunnelBufferSize = 4096;
constexpr int kTunnelCacheSize  = 2048 * 1024;
struct Tunnel {
    enum class Type : uint8_t {
        Main,
        Side
    };

    Tunnel( TunnelMgmt& mgmt_, struct bufferevent* bev_, int id_ );
    void assign( int id_, struct bufferevent* bev_ );
    void set_through();
    int  id() const { return _id; }

    virtual ~Tunnel();

    virtual size_t process( void* data_, size_t length_ ) { return length_; }
    virtual void   recv(){};
    virtual int    send( const void* data_, size_t length_ );
    virtual void   close();

    virtual void connected();
    virtual void disconnected( bool abnormal_ );

    void                bind_addr( const char* ip_, uint16_t port_ );
    struct sockaddr_in& addr() { return _sin; }
    struct bufferevent* bev() { return _bev; }
    TunnelMgmt&         mgmt() { return _mgmt; }

protected:
    TunnelMgmt& _mgmt;
    bool        _get_through;
    int         _id;
    size_t      _received;
    size_t      _length;

    struct sockaddr_in                  _sin = { 0 };
    struct bufferevent*                 _bev;
    std::array<char, kTunnelBufferSize> _message;

    std::unique_ptr<char[]> _cache;
    int                     _cc_length;
};

struct MainTunnel : Tunnel {
    using Tunnel::Tunnel;

    void recv() override;

    virtual void close();

protected:
    virtual void msg_received( const MsgHeader& msg_ );

private:
    void on_unknown( const MsgHeader& msg_ );
};

struct SrvTunnel : MainTunnel {
    using MainTunnel::MainTunnel;

    void msg_received( const MsgHeader& msg_ ) override;

private:
    void on_data( int chan_, void* data_, int length_ );
    void on_confirm( int chan_, const MsgConnConfirm& confirm_ );
    void on_config( const MsgConfig& cfg_ );
    void on_heartbeat();
};

struct CliTunnel : MainTunnel {
    using MainTunnel::MainTunnel;

    void set_cfgs( const LrpcContext::TunnelRepo cfgs_ ) { _cfgs = cfgs_; }
    void connected() override;
    void msg_received( const MsgHeader& msg_ ) override;

private:
    void on_conn( int chan_, uint16_t port_ );
    void on_data( int chan_, void* data_, int length_ );
    void on_heartbeat();
    void confirm( bool valid_ );
    void config();

private:
    LrpcContext::TunnelRepo _cfgs;
};

struct SideTunnel : Tunnel {
    using Tunnel::Tunnel;
    void recv() override;
};

}  // namespace lrp
#endif /* B3F5A80A_3838_4404_840C_1563CE17D70B */
